﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AMOOF {
    public class Statistics {

        public double Mean { set { mean = value; } get { return mean; } }
        public double Median { set { median = value; } get { return median; } }
        public double StdDev { set { stdDev = value; } get { return stdDev; } }
        public double FirstQuartile { set { firstQuart = value; } get { return firstQuart; } }
        public double ThirdQuartile { set { thirdQuart = value; } get { return thirdQuart; } }
        public Dictionary<int, double> DataMoments { set; get; }
        public double[] PositiveMoments { set { posMoments = value; } get { return posMoments; } }
        public double[] NegativeMoments { set { negMoments = value; } get { return negMoments; } }
        public Dictionary<int, double> VaRs { set { varDict = value; } get { return varDict; } }
        public Dictionary<int, double> CTEs { set { cteDict = value; } get { return cteDict; } }
        //public variables for accessing results
        private double mean = 0, median = 0, stdDev = 0, firstQuart = 0,
            thirdQuart = 0;
        //public double KSTestResult =0;
        private double[] posMoments = new double[4];
        private double[] negMoments = new double[4];
        private double[] dataPoints = null, origData = null;
        private Dictionary<int, double> varDict = new Dictionary<int, double>();
        private Dictionary<int, double> cteDict = new Dictionary<int, double>();

        private bool calculated = false;

        public string ToString() {
            string s = "Mean: ".PadRight(15) + Mean + "".PadRight(15) + mean + "\r\n"
                + "Median: ".PadRight(15) + Median + "".PadRight(15) + median + "\r\n"
                + "StdDev: ".PadRight(15) + StdDev + "".PadRight(15) + stdDev + "\r\n"
                + "FirstQuartile: ".PadRight(15) + FirstQuartile + "".PadRight(15) + firstQuart + "\r\n"
                + "ThirdQuartile: ".PadRight(15) + ThirdQuartile + "".PadRight(15) + thirdQuart + "\r\n";

            s += "\r\nData Moments: \r\n\r\n";
            for (int i = 0; i < DataMoments.Count; i++) {
                s += DataMoments.ElementAt(i).Key + "".PadRight(5)
                    + DataMoments.ElementAt(i).Value + "\r\n";
            }

            s += "\r\nPositive Moments: \r\n\r\n";
            for (int i = 0; i < PositiveMoments.Length; i++) {
                s += PositiveMoments.ElementAt(i) + "\r\n";
            }

            s += "\r\n Negative Moments: \r\n\r\n";
            for (int i = 0; i < NegativeMoments.Length; i++) {
                s += NegativeMoments.ElementAt(i) + "\r\n";
            }

            s += "\r\nVARs: \r\n\r\n";
            for (int i = 0; i < VaRs.Count; i++) {
                s += VaRs.ElementAt(i).Key + "".PadRight(5)
                    + VaRs.ElementAt(i).Value + "\r\n";
            }

            s += "\r\nCTEs: \r\n\r\n";
            for (int i = 0; i < CTEs.Count; i++) {
                s += CTEs.ElementAt(i).Key + "".PadRight(5)
                    + CTEs.ElementAt(i).Value + "\r\n";
            }
            return s;
        }

        public Statistics() {
            //initialize object and globals and do nothing if no dataset is passed
        }

        public Statistics getAllStatistics() {
            if (!calculated) {
                this.calculate();
            }
            return this;
        }

        public void Clear() {
            mean = 0;
            median = 0;
            stdDev = 0;
            firstQuart = 0;
            thirdQuart = 0;
            posMoments = new double[4];
            negMoments = new double[4];
            dataPoints = null;
            origData = null;
            varDict.Clear();
            cteDict.Clear();
        }

        public Statistics(Dictionary<double, int> data) {
            List<double> list = new List<double>();
            foreach (var kvp in data) {
                for (int i = 0; i < kvp.Value; i++) {
                    list.Add(kvp.Key);
                }
            }
            dataPoints = list.ToArray<double>();
            origData = dataPoints;
            Array.Sort(dataPoints);
            calculate();
            calculated = true;
        }

        //overloaded constructor takes in a double array of the data
        public Statistics(double[] data) {
            //saves origional data, sort dataPoints for calculations
            dataPoints = data;
            origData = data;
            Array.Sort(dataPoints);
        }

        //another overloaded constructor accounts for datasets that contain frequency
        public Statistics(List<Tuple<double, int>> freqList) {
            addData(freqList);
            calculate();
        }

        private void addData(List<Tuple<double, int>> freqList) {
            int count = 0;

            //count up the freqencies in order to allocate array to hold all values
            for (int i = 0; i < freqList.Count; i++) {
                count += freqList[i].Item2;
            }
            dataPoints = new double[count];
            int index = 0;

            //nested for loop to add all values 
            //(ex. (val = 1.0, freq=3) adds 1.0,1.0,1.0 to the dataPoints array
            for (int i = 0; i < freqList.Count; i++) {
                for (int j = 0; j < freqList[i].Item2; j++) {
                    dataPoints[index] = freqList[i].Item1;
                    index++;
                }
            }

            //save origional data then sort dataPoints for calculations
            origData = dataPoints;
            Array.Sort(dataPoints);
        }

        //method facilitates all initial stat calculations 
        private void calculate() {

            DataMoments = new Dictionary<int, double>();
            //run calculations
            mean = calcMean();
            median = calcMedian();
            stdDev = calcStdDev();
            calcQuartiles();
            buildVarDict();
            buildCteDict();
            //KSTest(dataPoints); commented out for future use

            //find all moments -4 through 4
            for (int i = 0; i < 4; i++) {
                posMoments[i] = calcMoments(i + 1);

                negMoments[i] = calcMoments(-(i + 1));
                DataMoments.Add((i + 1), posMoments[i]);
                DataMoments.Add(-(i + 1), negMoments[i]);
            }
            calculated = true;
        }

        private double calcMean() {

            //straight forward mean calculation
            double sum = 0;
            foreach (double point in dataPoints) {
                sum += point;
            }
            return sum / (double)dataPoints.Length;
        }


        //method outputs the median value.
        private double calcMedian() {

            double temp = 0;

            //if the number of values isnt divisible by 2        
            if (dataPoints.Length % 2 != 0) {
                //save the the middle element 
                temp = dataPoints[((dataPoints.Length) / 2)];
            }

            //if the number of elements is even, find middle 2 elements and average them.
            else {
                int middle = dataPoints.Length / 2;
                double first = dataPoints[middle];
                double second = dataPoints[middle - 1];
                temp = (first + second) / 2;
            }

            return temp;
        }


        //calculates standard deviation
        private double calcStdDev() {
            double total = 0;

            //array to hold each value for calculation of Standard Deviation
            double[] temp = new double[dataPoints.Length];
            for (int i = 0; i < dataPoints.Length; i++) {
                //find difference of each dataPointspoint to mean store it in temp
                //square the result, take the mean
                temp[i] = Math.Pow((dataPoints[i] - mean), 2);
                total += temp[i];
            }

            //square root the mean for standard deviation
            return Math.Sqrt(total / (double)temp.Length - 1);
        }


        //method calculates the 1st/3rd quartiles
        private void calcQuartiles() {
            //since VaR can be found for any percentile, 
            //find VaR for 25/75 percentiles
            //or the 1st/3rd quartiles
            firstQuart = calcValAtRisk(25);
            thirdQuart = calcValAtRisk(75);

        }

        //method calculates the moment at a given moment.
        private double calcMoments(int desiredMoment) {
            double result = 0;
            //sum values
            for (int i = 0; i < dataPoints.Length; i++) {
                result += Math.Pow(dataPoints[i], desiredMoment);
            }
            //average sum
            result = result / dataPoints.Length;
            return result;
        }

        //private void KSTest(double[] data)
        //{
        //    double max = -int.MaxValue;
        //    double min = int.MaxValue;
        //    for (int i = 1; i > data.Length; i++)
        //    {
        //        double yValAtI = data[i];
        //        max = Math.Max(max, (i / data.Length - yValAtI)); //where 1<=i<=n
        //    }
        //    for (int i = 1; i > data.Length; i++)
        //    {
        //        double yValAtI = data[i];
        //        min = Math.Max(max, (yValAtI - (i - 1) / data.Length));
        //    }
        //    double KPlusMax = Math.Sqrt(data.Length) * max;
        //    double KMinusMax = Math.Sqrt(data.Length) * min; //where 1<=i<=n
        //    KSTestResult = Math.Max(KPlusMax, KMinusMax);

        //}

        //returns value at risk for a given percentage: 0-100%
        public double calcValAtRisk(int percent) {
            return dataPoints[getVARIndex(percent)];
        }

        private int getVARIndex(int percent) {
            //if the val at risk percentage is 100%, return last element
            if (percent >= 100.0) return dataPoints.Length - 1;

            //if it isnt 100, find the position of the VaR
            double proportion = percent / 100.0;
            double maxIndex = dataPoints.Length - 1;
            double desiredIndex = proportion * maxIndex;
            int actualIndex = -1;
            double leftOver = desiredIndex % 1.0;
            if (leftOver < .5) {
                actualIndex = (int)desiredIndex;
            }
            else {
                actualIndex = (int)desiredIndex + 1;
            }
            return actualIndex;
        }

        public double calcCTE(int percent) {
            //find "p"th percentile for VaR, average out values below this for CTE
            int varIndex = getVARIndex(percent);
            double sum = 0;
            double count = 0;
            for (int i = 0; i <= varIndex; i++) {
                sum += dataPoints[i];
                count++;
            }
            return sum / count;
        }

        private void buildVarDict() {
            //builds a VaR dictionary at 5% increments
            varDict.Add(1, calcValAtRisk(1));
            for (int i = 5; i < 100; i += 5) {
                varDict.Add(i, calcValAtRisk(i));
            }
            varDict.Add(99, calcValAtRisk(99));
        }

        private void buildCteDict() {
            //builds a CTE dictionary at 5% increments
            cteDict.Add(1, calcCTE(1));
            for (int i = 5; i < 100; i += 5) {
                cteDict.Add(i, calcCTE(i));
            }
            cteDict.Add(99, calcCTE(99));
        }
    }
}
